package com.gopay.common.util.xml.xmlutils.tools;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gopay.common.util.xml.constants.BeanType;
import com.gopay.common.util.xml.utils.Assert0;
import com.gopay.common.util.xml.utils.XmlBeansUtils;
import com.gopay.common.util.xml.xmlutils.message.Message;
import com.gopay.common.util.xml.xmlutils.message.xml.vo.BFNode;
import com.gopay.common.util.xml.xmlutils.message.xml.vo.Bean;
import com.gopay.common.util.xml.xmlutils.message.xml.vo.Field;

/**
 * 
 * @author liujy
 * 
 */
public class J2XUtilTools {
	
	private static final Logger logger = LoggerFactory.getLogger(J2XUtilTools.class);

	private J2XUtilTools() {

	}

	/**
	 * 
	 * @param message
	 * @return
	 */
	static String builderXml(Message message, Object data,
			String encoding) {

		Assert0.notNull(message, "The message must not be null.");
		Assert0.notNull(data, "The data must not be null.");

		Document doc = DocumentHelper.createDocument();
		doc.setXMLEncoding(encoding);

		try {

			List<Bean> beans = message.getBeans();

			Assert0.notEmpty4List(beans, "The message.beans must not be empty.");

			for (int i = 0, size = beans.size(); i < size; i++) {
				parseBean(doc, beans.get(i), data);
			}

		} catch (Throwable e) {
			throw new RuntimeException("builder xml error, msgType："
					+ message.getMsgType() + ", dataClass: "
					+ data.getClass().getName(), e);
		}

		return doc.asXML().trim();
	}

	/**
	 * 
	 * @param bean
	 * @param newDoc
	 * @param data
	 * @throws Exception
	 */
	@SuppressWarnings("rawtypes")
	private static void parseBean(Node node, Bean bean, Object data)
			throws Exception {

		Assert0.notNull(bean, "The bean must not be null.");
		Assert0.notEmpty4String(bean.getBeanType(), "The bean.beanType must not be empty");
		Assert0.notEmpty4List(bean.getNodes(), "The bean.getNodes must not be empty.");

		if (BeanType.beanTypeMain.toString().equals(bean.getBeanType())) {
			List<BFNode> fields = bean.getNodes();
			for (int j = 0, size = null == fields ? 0 : fields.size(); j < size; j++) {
				pasreField(node, fields.get(j), data);
			}
			return;
		}

		if (BeanType.beanTypeList.toString().equals(bean.getBeanType())) {

			Assert0.notEmpty4String(bean.getName(), "The bean.name must not be empty");

			List items = (List) XmlBeansUtils.getPropertyObject(data, bean.getName());
			if(items == null || items.isEmpty()){
				logger.debug("The data.name be empty, data:" + data + "name: " + bean.getName());
				return;
			}

			String eleListPaths[] = bean.getPath().split("/");// 循环体跟元素在xml文件中的层级结构
			String elsPath[] = Arrays.copyOf(eleListPaths, eleListPaths.length - 1);// 循环体的父级元素层级结构
			
			Node parentListEle = null;
			if(null != elsPath && elsPath.length > 0){
			    parentListEle = createCurrEle(node, elsPath);// 创建循环体的父级元素
			}else{
			    parentListEle = node;
			}

			List<BFNode> bfNodes = bean.getNodes();
			for (int j = 0, size = items.size(); items != null && j < size; j++) {
				String parentEleName = eleListPaths[eleListPaths.length - 1];
				
				Element parentEle = null;
				if(parentListEle instanceof Element){
				    parentEle = ((Element)parentListEle).addElement(parentEleName);// 创建循环体根元素
				}else{
				    parentEle = ((Document)parentListEle).addElement(parentEleName);// 创建循环体根元素
				}
				for (int k = 0, fSize = bfNodes.size(); k < fSize; k++) {
					pasreField(parentEle, bfNodes.get(k), items.get(j));
				}
			}

			return;
		}

		throw new IllegalArgumentException("Element bean Attribute beanType："
				+ bean.getNodes());
	}

	/**
	 * 解析field
	 * 
	 * @param doc
	 * @param upEle
	 * @param currE
	 * @param source
	 * @throws Exception
	 */
	private static void pasreField(Node parentNode, BFNode bfNode, Object data)
			throws Exception {
		Assert0.notNull(bfNode, "The bean.node must not be null.");
		if (bfNode instanceof Bean) {
			parseBean(parentNode, (Bean) bfNode, data);
			return;
		} else if (bfNode instanceof Field) {
			
			Field f = (Field)bfNode;
			String elePaths[] = f.getPath().split("/");// path.split("/")
			
			String text = XmlBeansUtils.getPropertyString(data, f.getName(), f.getDefaultValue());
			if(null == text){
			    text = "";
			}
			
			if(f.isElement()){
				Element e = (Element)createCurrEle(parentNode, elePaths);
				e.setText(text);
			}else{
			    Element e = null;
			    if(elePaths.length > 1){
			       e = (Element)createCurrEle(parentNode, Arrays.copyOf(elePaths, elePaths.length - 1));
			    }else{
			        e = (Element)parentNode;
			    }
				e.addAttribute(elePaths[elePaths.length - 1], text);
			}
			return;
		}
		throw new IllegalArgumentException("bfNode classtype is invalid: "
				+ bfNode);
	}
	
	/**
	 * 
	 * @param doc
	 * @param upEle
	 * @param elsPath
	 * @return
	 * @throws Exception
	 */
	private static Node createCurrEle(Node parentNode, String elsPath[])
			throws Exception {
		Node currentEle = parentNode;
		for (int i = 0, length = elsPath.length; i < length; i++) {
			currentEle = getCurrentElement(parentNode, elsPath[i]);
			parentNode = currentEle;
		}
		return currentEle;
	}

	/**
	 * 创建当前元素层级结构
	 * 
	 * @param doc
	 * @param upEle
	 * @param eleName
	 * @return
	 */
	private static Element getCurrentElement(Node node, String eleName) {
		Element e = null;
		if (node instanceof Document) {
			e = ((Document) node).getRootElement();
			e = e == null ? ((Document) node).addElement(eleName) : e;
		} else {
			e = ((Element) node).element(eleName);
			e = e == null ? ((Element) node).addElement(eleName) : e;
		}
		return e;
	}

	/**
	 * 将map转为xml
	 */
	public static String builderXml4Map(Map<String, ? extends Object> map, String rootElemName, String encode) {
		Document doc = DocumentHelper.createDocument();
		doc.setXMLEncoding(encode);
		
		Element root = doc.addElement(rootElemName);
		for(Entry<String, ? extends Object> entry : map.entrySet()) {
			root.addElement(entry.getKey()).setText(entry.getValue().toString());
		}
		return doc.asXML();
	}
}